package com.rw.engine;

import com.rw.tool.util.json.CastUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;

/**
 * 公共自定义拦截器
 * 在Spring-Engine中，有许多自定义的拦截器
 * 由于这些拦截器的代码都高度相似，需要提高代码复用
 * 于是编写除了这个公共自定义拦截器
 * <p>
 * Spring-Engine拦截器都有一个特点都是根据方法或者类上的注解来判断是否放行
 * 所以泛型是注解类型
 */
public abstract class CommonCustomInterceptor<CustomAnnotation extends Annotation> implements HandlerInterceptor {
    // 规则过滤器的过滤方法名，用于反射调用
    private static final String FILTER_RULE = "filterRule";

    /**
     * 自定义的注解
     * 自定义拦截器需要根据这个注解来判断是否放行
     */
    protected Class<CustomAnnotation> customClass;

    /**
     * 创建自定义拦截器
     *
     * @param customClass 自定义的注解，自定义拦截器需要根据这个注解来判断是否放行
     */
    protected CommonCustomInterceptor(Class<CustomAnnotation> customClass) {
        this.customClass = customClass;
    }

    /**
     * Spring容器
     * 我们的过滤规则需要从Spring容器里面拿取
     * 以确保用户可以自定义过滤规则
     */
    @Resource
    protected ApplicationContext applicationContext;

    /**
     * 拦截公共的逻辑
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 如果目标请求不是方法，则放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // 获取对应的注解对象，用于调用之后的过滤规则用
        Optional<CustomAnnotation> annotationOptional = getAnnotation(handlerMethod.getMethod(), customClass);
        if (!annotationOptional.isPresent()) {
            return true;
        }
        // 注解对象
        CustomAnnotation annotation = annotationOptional.get();

        // 获取公共的Limit注解
        MergedAnnotation<CommonLimit> commonLimit = getMergedAnnotation(handlerMethod.getMethod(), annotation);

        // 获取注解上的过滤规则数组
        Class<? extends CommonFilterRule<? extends Annotation>>[] filterRules = getFilterRules(commonLimit);

        // 如果过滤规则为空，则直接返回
        if (filterRules == null) {
            return true;
        }

        // 遍历规则数组，转化并过滤
        for (Class<? extends CommonFilterRule<? extends Annotation>> filterRuleClass : filterRules) {
            // 从Spring容器里面拿出规则过滤器
            CommonFilterRule<? extends Annotation> commonFilterRule = findFilterRule(filterRuleClass);

            // 规则过滤器的过滤方法名，用于反射调用
            final String FILTER = "filter";
            // 因为无法保证注解中的泛型和拦截器中的一致，所以直接使用反射。
            // 如果不一致则会抛出方法找不到异常
            Method filter = filterRuleClass.getMethod(FILTER, customClass, HttpServletRequest.class);
            try {
                filter.invoke(commonFilterRule, annotation, request);
            } catch (InvocationTargetException e) {
                throw (Exception) e.getTargetException();
            }
        }
        return true;
    }

    /**
     * 尝试从Spring容器里取出过滤规则
     * 如果取出失败，则构建一个对象
     *
     * @return 取出或构建的对象
     */
    private <CustomFilterRule> CustomFilterRule findFilterRule(Class<CustomFilterRule> filterRuleClass)
            throws InstantiationException, IllegalAccessException {
        try {
            return applicationContext.getBean(filterRuleClass);
        } catch (BeansException exception) {
            return filterRuleClass.newInstance();
        }
    }

    /**
     * 获取当前方法上的对应注解
     * 若方法上没有，则去对应类上寻找
     */
    private <T extends Annotation> Optional<T> getAnnotation(Method method, Class<T> annotationClass) {
        T annotation = method.getAnnotation(annotationClass);
        // 如果在方法上没有获得注解，则尝试获取类上的注解
        if (annotation == null) {
            annotation = method.getDeclaringClass().getAnnotation(annotationClass);
        }
        return Optional.ofNullable(annotation);
    }

    /**
     * 获得方法上 CommonLimit 注解
     * 若方法上没有，则去对应类上寻找
     * 这个方法能够找到通过继承之后的CommonLimit子类注解
     */
    private MergedAnnotation<CommonLimit> getMergedAnnotation(Method method, Annotation annotation) {
        // 获取注解的合并视图
        MergedAnnotations mergedAnnotations = MergedAnnotations.from(method, annotation);
        MergedAnnotation<CommonLimit> mergedAnnotation = mergedAnnotations.get(CommonLimit.class);
        if (mergedAnnotation.isPresent()) {
            return mergedAnnotation;
        }

        // 直接从接口上获得合并的注解
        mergedAnnotations = MergedAnnotations.from(method.getDeclaringClass(), annotation);
        mergedAnnotation = mergedAnnotations.get(CommonLimit.class);
        if (mergedAnnotation.isPresent()) {
            return mergedAnnotation;
        }

        throw new GetParentsAnnotationException("Failed to get parent annotation");
    }

    /**
     * 获取过滤规则
     *
     * @param mergedAnnotation 合并视图
     * @return 过滤规则
     */
    private Class<? extends CommonFilterRule<? extends Annotation>>[] getFilterRules(
            MergedAnnotation<CommonLimit> mergedAnnotation) {
        // 获得过滤规则值
        Optional<Object> value = mergedAnnotation.getValue(FILTER_RULE, Object.class);
        // 如果不存在，则返回null
        if (!value.isPresent()) {
            return null;
        }

        // 解析
        Object filterRule = value.get();
        return CastUtil.castObj(filterRule);
    }
}
